﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Web;
using System.Xml;
using Cognifide.PowerShell.Shell.Provider;
using Cognifide.PowerShell.Shell.Settings;
using System.Linq;

namespace Cognifide.PowerShell.Shell.Host
{
    public class ScriptSession : IDisposable
    {
        private Runspace runspace;
        private ScriptingHost host;

        private string htmlExceptionFormatString = "<div class='white-space:normal; width:70%;'>{1}</div>{0}<strong>Of type</strong>: {3}{0}<strong>Stack trace</strong>:{0}{2}{0}";
        private string htmlInnerExceptionPrefix = "{0}<strong>Inner Exception:</strong>{1}";
        private string lineEndformat = "\n";

        private bool disposed;
        private bool initialized;
        public List<OutputLine> Output { get; private set; }
        static readonly List<CmdletConfigurationEntry> commandlets = new List<CmdletConfigurationEntry>();
        public string CurrentLocation { get { return runspace.SessionStateProxy.Path.CurrentLocation.Path; } }

        static ScriptSession()
        {
            //Assembly assembly = typeof (ScriptSession).Assembly;
            //GetCommandletsFromAssembly(assembly);

            var cmdltsToIncludes = Sitecore.Configuration.Factory.GetConfigNodes("powershell/commandlets/add");
            foreach (XmlElement cmdltToInclude in cmdltsToIncludes)
            {
                string[] cmdltTypeDef = cmdltToInclude.Attributes["type"].Value.Split(',');
                string cmdletType = cmdltTypeDef[0];
                string cmdletAssembly = cmdltTypeDef[1];
                var wildcard = GetWildcardPattern(cmdletType);
                var assembly = Assembly.Load(cmdletAssembly);
                GetCommandletsFromAssembly(assembly, wildcard);
            }
        }

        static protected WildcardPattern GetWildcardPattern(string name)
        {
            if (String.IsNullOrEmpty(name))
            {
                name = "*";
            }
            WildcardOptions options = WildcardOptions.IgnoreCase | WildcardOptions.Compiled;
            WildcardPattern wildcard = new WildcardPattern(name, options);
            return wildcard;
        }

        private static void GetCommandletsFromAssembly(Assembly assembly, WildcardPattern wildcard)
        {
            foreach (Type type in assembly.GetTypes())
            {
                if (type.GetCustomAttributes(typeof (CmdletAttribute), true).Length > 0 && wildcard.IsMatch(type.FullName))
                {
                    CmdletAttribute attribute = (CmdletAttribute) (type.GetCustomAttributes(typeof (CmdletAttribute), true)[0]);
                    commandlets.Add(new CmdletConfigurationEntry(attribute.VerbName + "-" + attribute.NounName, type, ""));
                }
            }
        }

        public ScriptSession()
        {
            // Create and open a PowerShell runspace.  A runspace is a container 
            // that holds PowerShell pipelines, and provides access to the state
            // of the Runspace session (aka SessionState.)
            RunspaceConfiguration conf = RunspaceConfiguration.Create();
            host = new ScriptingHost();
            host.UI.RawUI.BufferSize = new Size(PsConsoleSettings.Instance.HostWidth,Int32.MaxValue);
            InitialSessionState initState = InitialSessionState.CreateDefault();
            initState.ThreadOptions = PSThreadOptions.UseCurrentThread;
            initState.ApartmentState = System.Threading.ApartmentState.STA;
            runspace = RunspaceFactory.CreateRunspace(host, conf);
            conf.Cmdlets.Append(commandlets);
            if (PsConsoleSettings.Instance.UseTypeInfo)
            {
                conf.Formats.Prepend(
                    new FormatConfigurationEntry(HttpRuntime.AppDomainAppPath + @"Console\Assets\Sitecore.Views.ps1xml"));
                conf.Formats.Update();
                conf.Types.Prepend(
                    new TypeConfigurationEntry(HttpRuntime.AppDomainAppPath + @"Console\Assets\Sitecore.Types.ps1xml"));
                conf.Types.Update();
            }

            runspace.ThreadOptions = PSThreadOptions.UseCurrentThread;
            runspace.ApartmentState = System.Threading.ApartmentState.STA;
            PsSitecoreItemProvider.AppendToRunSpace(runspace.RunspaceConfiguration);
            runspace.Open();           
        }

        public void Initialize()
        {
            lock (this)
            {
                if (!initialized)
                {
		            initialized = true;
		            runspace.SessionStateProxy.SetVariable("AppPath", HttpRuntime.AppDomainAppPath);
		            runspace.SessionStateProxy.SetVariable("AppVPath", HttpRuntime.AppDomainAppVirtualPath);
		            runspace.SessionStateProxy.SetVariable("tempPath", Environment.GetEnvironmentVariable("temp"));
		            runspace.SessionStateProxy.SetVariable("tmpPath", Environment.GetEnvironmentVariable("tmp"));
		            runspace.SessionStateProxy.SetVariable("me", HttpContext.Current.User.Identity.Name);
		            runspace.SessionStateProxy.SetVariable("HttpContext", HttpContext.Current);
		            runspace.SessionStateProxy.SetVariable("request", HttpContext.Current.Request);
		            runspace.SessionStateProxy.SetVariable("response", HttpContext.Current.Response);
		            runspace.SessionStateProxy.SetVariable("ClientData", Sitecore.Context.ClientData);
		            runspace.SessionStateProxy.SetVariable("ClientPage", Sitecore.Context.ClientPage);
                }
            }
        }

        public List<object> ExecuteScriptPart(string script)
        {
            return ExecuteScriptPart(script, true, false);            
        }

        public List<object> ExecuteScriptPart(string script, bool stringOutput)
        {
            return ExecuteScriptPart(script, stringOutput, false);                        
        }

        private List<object> ExecuteScriptPart(string script, bool stringOutput, bool internalScript)
        {
            if (!initialized)
            {
                Initialize();
            }

            if (String.IsNullOrEmpty(script))
            {
                return null;
            }

            // Create a pipeline, and populate it with the script given in the
            // edit box of the form.
            Pipeline pipeline = runspace.CreatePipeline(script);

            if (!internalScript)
            {
                pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
            }

            if (stringOutput)
            {
                pipeline.Commands.Add("out-default");
            }
            pipeline.StateChanged += new EventHandler<PipelineStateEventArgs>(pipeline_StateChanged);
            
            // execute the commands in the pipeline now
            Collection<PSObject> execResults = pipeline.Invoke();

            Output = host.Output;

            var results = execResults.Select(p => p.BaseObject).ToList();
            return results;
        }


        void pipeline_StateChanged(object sender, PipelineStateEventArgs e)
        {
            
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                    runspace.Dispose();
                }

                // There are no unmanaged resources to release, but
                // if we add them, they need to be released here.
            }
            disposed = true;

            // If it is available, make the call to the
            // base class's Dispose(Boolean) method
            //base.Dispose(disposing);
        }

        #region IDisposable logic
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~ScriptSession()
        {
            Dispose(false);
        }

        public void Close()
        {
            runspace.Dispose();
        }

        #endregion

        public string GetExceptionString(Exception exc)
        {
            string exception = String.Empty;            
            exception += String.Format(
                htmlExceptionFormatString,
                lineEndformat,
                exc.Message, 
                exc.StackTrace.Replace("\n", lineEndformat),
                exc.GetType());
            if (exc.InnerException != null)
            {
                exception += String.Format(htmlInnerExceptionPrefix, lineEndformat, GetExceptionString(exc.InnerException));
            }
            return exception;
        }

        private string consoleExceptionFormatString = 
            "[[;#f00;#000]Exception: {0}]\r\n"+
            "[[;#f00;#000]Type: {1}]\r\n"+
            "[[;#f00;#000]Stack trace:\r\n{2}]";
        private string consoleInnerExceptionPrefix = "\r\n[[b;#fff;#F00]Inner ] {0}";

        public string GetExceptionConsoleString(Exception exc)
        {
            string exception = String.Empty;
            exception += String.Format(
                consoleExceptionFormatString,
                exc.Message,
                exc.GetType(),
                exc.StackTrace.Replace("[", "%((%").Replace("]", "%))%"));
            if (exc.InnerException != null)
            {
                exception += String.Format(consoleInnerExceptionPrefix, GetExceptionConsoleString(exc.InnerException));
            }
            return exception;
        }

    }
}
